﻿using System;
using System.Buffers;
using System.Buffers.Binary;
using System.IO;
using System.Runtime.CompilerServices;

namespace ManagedTFRecordLibrary
{
    public sealed class TFRecordWriter : IDisposable
    {
        private Stream _stream;
        private readonly bool _leaveOpen;

        public TFRecordWriter(Stream stream, bool leaveOpen)
        {
            if (stream == null)
            {
                throw new ArgumentNullException(nameof(stream));
            }
            _stream = stream;
            _leaveOpen = leaveOpen;
        }

        public void Write(byte[] data, int offset, int count)
        {
            if (data.Length < offset + count)
            {
                throw new ArgumentException();
            }

            CheckDisposed();

            uint maskedCrc;
            Crc32 crc32 = new Crc32(Crc32.CastagnoliPolynomial);

#if true
            byte[] bytesBuffer = ArrayPool<byte>.Shared.Rent(12);

            try
            {
                Span<byte> buffer = bytesBuffer;
#else
            Span<byte> buffer = stackalloc byte[12];
#endif
                BinaryPrimitives.WriteUInt64LittleEndian(buffer, (ulong)count);
                maskedCrc = MaskCrc32(crc32.Append(buffer.Slice(0, 8)).Value);
                BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(8, 4), maskedCrc);

                maskedCrc = MaskCrc32(crc32.Append(data, offset, count).Value);
#if true
                _stream.Write(bytesBuffer, 0, 12);
                _stream.Write(data, offset, count);
                BinaryPrimitives.WriteUInt32LittleEndian(buffer, maskedCrc);
                _stream.Write(bytesBuffer, 0, 4);

            }
            finally
            {
                ArrayPool<byte>.Shared.Return(bytesBuffer);
            }
#else
            _stream.Write(buffer.Slice(0, 12));
            _stream.Write(data, offset, count);
            BinaryPrimitives.WriteUInt32LittleEndian(buffer, maskedCrc);
            _stream.Write(buffer.Slice(0, 4));
#endif
        }


        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static uint MaskCrc32(uint crc32)
        {
            unchecked
            {
                return (uint)(((crc32 >> 15) | (crc32 << 17)) + 0xa282ead8ul);
            }
        }


        private void CheckDisposed()
        {
            if (_stream == null)
            {
                throw new ObjectDisposedException(nameof(TFRecordWriter));
            }
        }
        public void Dispose()
        {
            if (_stream != null && !_leaveOpen)
            {
                _stream.Dispose();
            }
            _stream = null;
        }
    }
}
